/*
 * Speck cipher implementation and testing code
 * (c) 2013 by Sebastian Gesemann
 *
 * Speck is a family of lightweight block ciphers designed by a team
 * of NSA members (see http://eprint.iacr.org/2013/404.pdf )
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <iostream>
#include "integer.hpp"
#include "speck.hpp"

namespace {

using std::cout;
using std::endl;

template<class T>
void print_hex(std::ostream& os, T v, int digits)
{
	for (int i=digits-1; i>=0; --i) {
		os << "0123456789abcdef"[(v >> (i*4)) & 0xF];
	}
}

template<int N>
std::ostream& operator<<(std::ostream& os, speck::block_t<N> const& s)
{
	print_hex(os,s.x,(3+N)/4);
	os << ' ';
	print_hex(os,s.y,(3+N)/4);
	return os;
}

inline bool verbose(bool ok)
{
	cout << "FAIL\n\0OK\n"+(ok*6);
	return ok;
}

template<int N>
inline bool operator==(speck::block_t<N> const& a, speck::block_t<N> const& b)
{
	return a.x==b.x && a.y==b.y;
}

template<int N>
inline bool operator!=(speck::block_t<N> const& a, speck::block_t<N> const& b)
{
	return !(a==b);
}

typedef integer::select_unsigned<16>::type uintleast16;
typedef integer::select_unsigned<24>::type uintleast24;
typedef integer::select_unsigned<32>::type uintleast32;
typedef integer::select_unsigned<48>::type uintleast48;
typedef integer::select_unsigned<64>::type uintleast64;

static const uintleast64 keywords[4] = {
	0x0706050403020100,
	0x0f0e0d0c0b0a0908,
	0x1716151413121110,
	0x1f1e1d1c1b1a1918,
};

static const uintleast16 p_32_64  [] = {0x6574, 0x694c};
static const uintleast16 c_32_64  [] = {0xa868, 0x42f2};
static const uintleast24 p_48_72  [] = {0x20796c, 0x6c6172};
static const uintleast24 c_48_72  [] = {0xc049a5, 0x385adc};
static const uintleast24 p_48_96  [] = {0x6d2073, 0x696874};
static const uintleast24 c_48_96  [] = {0x735e10, 0xb6445d};
static const uintleast32 p_64_96  [] = {0x74614620, 0x736e6165};
static const uintleast32 c_64_96  [] = {0x9f7952ec, 0x4175946c};
static const uintleast32 p_64_128 [] = {0x3b726574, 0x7475432d};
static const uintleast32 c_64_128 [] = {0x8c6fa548, 0x454e028b};
static const uintleast48 p_96_96  [] = {0x65776f68202c, 0x656761737520};
static const uintleast48 c_96_96  [] = {0x9e4d09ab7178, 0x62bdde8f79aa};
static const uintleast48 p_96_144 [] = {0x656d6974206e, 0x69202c726576};
static const uintleast48 c_96_144 [] = {0x2bf31072228a, 0x7ae440252ee6};
static const uintleast64 p_128_128[] = {0x6c61766975716520, 0x7469206564616d20};
static const uintleast64 c_128_128[] = {0xa65d985179783265, 0x7860fedf5c570d18};
static const uintleast64 p_128_192[] = {0x7261482066656968, 0x43206f7420746e65};
static const uintleast64 c_128_192[] = {0x1be4cf3a13135566, 0xf9bc185de03c1886};
static const uintleast64 p_128_256[] = {0x65736f6874206e49, 0x202e72656e6f6f70};
static const uintleast64 c_128_256[] = {0x4109010405c0f53e, 0x4eeeb48d9c188f43};

template<int N, int M, class W>
bool check_test_vector(
	const W p[2],
	const W c[2])
{
	static_assert(2<=M && M<=4, "illegal number of key words");
	typedef typename speck::block_t<N>::value_type value_type;
	speck::key_t<N,M> key;
	for (int i=0; i<M; ++i)
		key[i] = static_cast<value_type>(keywords[i] & integer::ops<N>::mask);
	auto subkeys = key_expand(key);
	speck::block_t<N> plaintxt = {p[0],p[1]};
	speck::block_t<N> ciphrtxt = {c[0],c[1]};
	bool ok = true;
	cout << "testing Speck-"<<(N*2)<<"/"<<(N*M)<<":\n";
	speck::block_t<N> st = plaintxt;
	cout << st << endl;
	cout << "\tencrypting...\n"; encrypt(st,subkeys);
	cout << st << " =?= " << ciphrtxt << ": ";
	ok &= verbose(st==ciphrtxt);
	cout << "\tdecrypting...\n"; decrypt(st,subkeys);
	cout << st << " =?= starting value: ";
	ok &= verbose(st==plaintxt);
	cout << "test: ";
	return verbose(ok);
}

} // anonymous namespace

int main()
{
	bool ok = true;
	#define TEST(nblock,nkey) \
	(ok &= check_test_vector<(nblock)/2,(nkey)/((nblock)/2)>(p_##nblock##_##nkey,c_##nblock##_##nkey))
	TEST(32,64); cout<<'\n';
	TEST(48,72); cout<<'\n';
	TEST(48,96); cout<<'\n';
	TEST(64,96); cout<<'\n';
	TEST(64,128); cout<<'\n';
	TEST(96,96); cout<<'\n';
	TEST(96,144); cout<<'\n';
	TEST(128,128); cout<<'\n';
	TEST(128,192); cout<<'\n';
	TEST(128,256);
	#undef TEST
	return ok ? 0 : 1;
}
